package com.google.code.jholidays.parsers;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.code.jholidays.events.FloatingEvent;
import com.google.code.jholidays.events.FloatingEventFactory;
import com.google.code.jholidays.events.parameters.DateParameters;
import com.google.code.jholidays.events.parameters.DayOfWeekOrder;

/**
 * Represents parser which can extract {@link DateParameters} from
 * {@link String} expression. This parser is used by
 * {@link FloatingEventFactory} in the process of construction of
 * {@link FloatingEvent} instances
 * 
 * @author tillias
 * @see FloatingEventFactory
 * @see FloatingEvent
 * @see DateParameters
 * 
 */
public class FloatingParser {

    /**
     * Parses given string and returns {@link DateParameters}
     * 
     * @param expression
     *            String which possible contains {@link DateParameters}
     * @return {@link DateParameters} if succeeded, null otherwise
     */
    public DateParameters parseParameters(String expression) {
	DateParameters result = null;

	Matcher matcher = pattern.matcher(expression);

	if (matcher.matches()) {
	    result = buildParameters(matcher);
	}

	return result;
    }

    /**
     * Checks whether given string represents {@link DateParameters}
     * 
     * @param expression
     *            String representation of {@link DateParameters}
     * @return True if input string represents {@link DateParameters} false
     *         otherwise
     */
    public boolean matches(String expression) {
	return pattern.matcher(expression).matches();
    }

    private DateParameters buildParameters(Matcher matcher) {
	DateParameters result = null;

	if (matcher.groupCount() == groupsCount) {

	    /*
	     * example: 4%Sun%Aug Fourth sunday of august
	     */

	    try {
		DayOfWeekOrder order = getOrder(matcher.group(1));

		String dayOfWeekStr = matcher.group(2);
		int dayOfWeek = getDayOfWeekIndex(dayOfWeekStr);

		String monthStr = matcher.group(3);
		int month = getMonthIndex(monthStr);

		if (month != -1 && dayOfWeek != -1) {
		    result = new DateParameters(dayOfWeek, month - 1, order);
		}
	    } catch (Exception e) {
		// nothing todo here
	    }

	}

	return result;
    }

    private DayOfWeekOrder getOrder(String orderName) {
	if (orderName.equalsIgnoreCase("L"))
	    return DayOfWeekOrder.Last;

	if (orderName.equals("1"))
	    return DayOfWeekOrder.First;
	if (orderName.equals("2"))
	    return DayOfWeekOrder.Second;
	if (orderName.equals("3"))
	    return DayOfWeekOrder.Third;
	if (orderName.equals("4"))
	    return DayOfWeekOrder.Fourth;

	throw new IllegalArgumentException("Invalid day of week in month order");
    }

    private int getMonthIndex(String monthName) {
	int result = -1;

	try {
	    switch (MonthName.valueOf(monthName)) {
	    case Jan:
		return 1;
	    case Feb:
		return 2;
	    case Mar:
		return 3;
	    case Apr:
		return 4;
	    case May:
		return 5;
	    case Jun:
		return 6;
	    case Jul:
		return 7;
	    case Aug:
		return 8;
	    case Sep:
		return 9;
	    case Oct:
		return 10;
	    case Nov:
		return 11;
	    case Dec:
		return 12;

	    default:
		break;
	    }
	} catch (Exception e) {
	    // nothing todo here
	}

	return result;
    }

    private int getDayOfWeekIndex(String dayOfWeekName) {
	int result = -1;

	try {
	    switch (DayOfWeek.valueOf(dayOfWeekName)) {
	    case Sun:
		return 1;
	    case Mon:
		return 2;
	    case Tue:
		return 3;
	    case Wed:
		return 4;
	    case Thu:
		return 5;
	    case Fri:
		return 6;
	    case Sat:
		return 7;
	    default:
		break;
	    }
	} catch (Exception e) {
	    // nothing todo here
	}

	return result;
    }

    private final static Pattern pattern = Pattern
	    .compile("([1-4L])%(Sun|Mon|Tue|Wed|Thu|Fri|Sat)%"
		    + "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)");

    final static int groupsCount = 3;

}

enum DayOfWeek {
    Sun, Mon, Tue, Wed, Thu, Fri, Sat;
}

enum MonthName {
    Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec;
}
